package org.infodavid.common.impl.services;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.PersistenceException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.infodavid.common.IBuilder;
import org.infodavid.common.dto.IUserGroupListItem.EUserRole;
import org.infodavid.common.model.IUser;
import org.infodavid.common.model.IUserGroup;
import org.infodavid.common.model.ModelFactory;
import org.infodavid.common.model.components.IEMail;
import org.infodavid.common.persistence.IDataObject;
import org.infodavid.common.persistence.ITransaction;
import org.infodavid.common.persistence.PersistenceSession;
import org.infodavid.common.persistence.UserDataService;
import org.infodavid.common.persistence.UserGroupDataService;
import org.infodavid.common.persistence.impl.PersistenceUtil;
import org.infodavid.common.services.IApplicationContext;
import org.infodavid.common.services.UserGroupService;
import org.infodavid.common.services.UserService;
import org.infodavid.common.services.exceptions.ServiceException;
import org.infodavid.common.util.PropertiesUtil;

/**
 * The Class ServiceUtil.
 */
public class ServiceUtil {

  /** The Constant log. */
  private static final Log LOGGER = LogFactory.getLog(ServiceUtil.class);

  /**
   * The Constructor.
   */
  protected ServiceUtil() {
    super();
  }

  /**
   * Validate.
   * @param context the context
   * @throws ServiceException the service exception
   */
  public static void validate(final IApplicationContext context) throws ServiceException {
    if (context == null) {
      throw new IllegalArgumentException("[context]: can not be null");
    }

    if (context.getPersistenceSession() == null) {
      throw new IllegalArgumentException("[persistence]: can not be null");
    }
  }

  /**
   * Cleanup.
   * @param context the context
   */
  public static void cleanup(final IApplicationContext context) {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Cleanup invoked");
    }

    if (context != null) {
      final PersistenceSession session = context.getPersistenceSession();

      if (session != null) {
        ITransaction transaction = null;

        try {
          transaction = session.getTransaction();

          if (transaction.isActive()) {
            transaction.rollback();
          }
        }
        catch (final Exception e) {
          LOGGER.warn("An error occurs while rolling back the transaction", e);
        }

        PersistenceUtil.release(session);
      }
    }
  }

  /**
   * Close the context without action on transaction.
   * @param context the context
   */
  public static void close(final IApplicationContext context) {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Closing context invoked");
    }

    if (context != null) {
      final PersistenceSession session = context.getPersistenceSession();

      if (session != null) {
        PersistenceUtil.release(session);
      }
    }
  }

  /**
   * Initialize.
   * @param session the session
   * @param adminPassword the admin password
   * @param adminEmail the admin email
   * @return the inserted users
   * @throws PersistenceException the persistence exception
   */
  public static Map<String,IUser> initializeSystemData(final PersistenceSession session,
      final String adminPassword, final String adminEmail) throws PersistenceException {
    final boolean isDebugEnabled = LOGGER.isDebugEnabled();
    final Map<String,IUser> results = new HashMap<String,IUser>();
    String administratorPassword = adminPassword;
    String administratorEmail = adminEmail;
    boolean manageTransaction = false;

    if (StringUtils.isEmpty(administratorPassword)) {
      administratorPassword = IUser.DEFAULT_PASSWORD;
    }

    if (StringUtils.isEmpty(administratorEmail)) {
      administratorEmail =
          PropertiesUtil.getInstance().getSystemPropertyValue(
              UserService.ADMINISTRATOR_EMAIL_SYSTEM_PROPERTY, IUser.DEFAULT_ADMINISTRATOR_EMAIL);
    }

    if (isDebugEnabled) {
      LOGGER.debug("Checking system: groups");
    }

    final ModelFactory factory = ModelFactory.getInstance();
    final UserDataService userDataService = UserDataService.getInstance();
    final List<IUser> users = new ArrayList<IUser>();
    final List<IUserGroup> groups = new ArrayList<IUserGroup>();

    try {
      // adding user groups
      userDataService.findByRole(session, EUserRole.ADMINISTRATORS, users, null);

      if (users.isEmpty()) {
        LOGGER.info("Initializing system data");

        final UserGroupDataService groupDataService = UserGroupDataService.getInstance();
        final IBuilder<IUserGroup> groupBuilder = factory.getBuilder(IUserGroup.class);
        final IBuilder<IUser> userBuilder = factory.getBuilder(IUser.class);
        final IUserGroup administrators;
        final IUserGroup operators;
        final IUserGroup system;
        final IUserGroup managers;
        final IUserGroup normal;
        final IUser administrator;
        final IUser user;

        manageTransaction = !session.isTransactionActive();

        if (manageTransaction) {
          session.getTransaction().begin();
        }

        if (isDebugEnabled) {
          LOGGER.debug("Checking system: groups");
        }

        groupDataService.findByRole(session, EUserRole.ADMINISTRATORS, groups, null);

        if (groups.isEmpty()) {
          administrators = groupBuilder.create();

          administrators.setGuest(false);
          administrators.setLocked(false);
          administrators.setComment("Group added automatically");// TODO resources bundle
          administrators.setName(UserGroupService.ADMINISTRATORS);
          administrators.setRole(EUserRole.ADMINISTRATORS);
          groupDataService.insert(session, administrators);
        }
        else {
          administrators = groups.get(0);
        }

        groups.clear();
        groupDataService.findByRole(session, EUserRole.SYSTEM, groups, null);

        if (groups.isEmpty()) {
          system = groupBuilder.create();

          system.setGuest(false);
          system.setLocked(false);
          system.setComment("Group added automatically");// TODO resources bundle
          system.setName(UserGroupService.SYSTEM);
          system.setRole(EUserRole.SYSTEM);
          groupDataService.insert(session, system);
        }
        else {
          system = groups.get(0);
        }

        groups.clear();
        groupDataService.findByRole(session, EUserRole.OPERATORS, groups, null);

        if (groups.isEmpty()) {
          operators = groupBuilder.create();

          operators.setGuest(false);
          operators.setLocked(false);
          operators.setComment("Group added automatically");// TODO resources bundle
          operators.setName(UserGroupService.OPERATORS);
          operators.setRole(EUserRole.OPERATORS);
          groupDataService.insert(session, operators);
        }
        else {
          operators = groups.get(0);
        }

        groups.clear();
        groupDataService.findByRole(session, EUserRole.MANAGERS, groups, null);

        if (groups.isEmpty()) {
          managers = groupBuilder.create();

          managers.setGuest(false);
          managers.setLocked(false);
          managers.setComment("Group added automatically");// TODO resources bundle
          managers.setName(UserGroupService.MANAGERS);
          managers.setRole(EUserRole.MANAGERS);
          groupDataService.insert(session, managers);
        }
        else {
          managers = groups.get(0);
        }

        groups.clear();
        groupDataService.findByRole(session, EUserRole.NORMAL, groups, null);

        if (groups.isEmpty()) {
          normal = groupBuilder.create();

          normal.setGuest(true);
          normal.setLocked(false);
          normal.setComment("Group added automatically");// TODO resources bundle
          normal.setName(UserGroupService.USERS);
          normal.setRole(EUserRole.NORMAL);
          groupDataService.insert(session, normal);
        }
        else {
          normal = groups.get(0);
        }

        // adding users
        userDataService.findByRole(session, EUserRole.ADMINISTRATORS, users, null);

        if (users.isEmpty()) {
          administrator = userBuilder.create();

          administrator.setComment("User added automatically");// TODO resources bundle
          administrator.setPassword(administratorPassword);
          administrator.setEmail(factory.create(IEMail.class));
          administrator.getEmail().setType(IEMail.EMailType.UNDEFINED);
          administrator.getEmail().setValue(administratorEmail);
          administrator.setExpirationDate(null);
          administrator.setLocked(false);
          administrator.setName(IUser.ADMINISTRATOR);
          administrator.setPrimaryGroup(administrators);
          userDataService.insert(session, administrator);
          results.put(administrator.getName(), administrator);
        }
        else {
          administrator = users.get(0);
        }

        users.clear();
        userDataService.findByRole(session, EUserRole.SYSTEM, users, null);

        if (users.isEmpty()) {
          user = userBuilder.create();

          user.setComment("User added automatically");// TODO resources bundle
          user.setPassword(IUser.DEFAULT_PASSWORD);
          user.setExpirationDate(null);
          user.setLocked(true);
          user.setEmail(factory.create(IEMail.class));
          user.getEmail().setType(administrator.getEmail().getType());
          user.getEmail().setValue(administrator.getEmail().getValue());
          user.setName(IUser.SYSTEM);
          user.setPrimaryGroup(system);
          user.setEmail(factory.create(IEMail.class));
          user.getEmail().setValue(administrator.getEmail().getValue());
          user.getEmail().setType(administrator.getEmail().getType());
          userDataService.insert(session, user);
          results.put(user.getName(), user);
        }

        if (manageTransaction) {
          session.getTransaction().commit();
        }
      }
    }
    catch (final Exception e) {
      if (manageTransaction) {
        try {
          session.getTransaction().rollback();
        }
        catch (final PersistenceException e1) {
          LOGGER.debug("An error occurs while rolling back transaction", e);
        }
      }

      throw new PersistenceException(e);
    }

    return results;
  }

  /**
   * Load lazy data.
   * @param properties the properties
   */
  public static void loadLazyData(final Map<String,Serializable> properties) {
    if (properties == null || properties.isEmpty()) {
      LOGGER.debug("No property, nothing to 'lazy' load");
    }
    else {
      Serializable value;

      for (final Map.Entry<String,Serializable> entry : properties.entrySet()) {
        value = entry.getValue();

        if (value instanceof IDataObject) {
          // TODO lazy load
        }
      }
    }
  }

  /**
   * Validate.
   * @param <T> the generic type
   * @param values the values
   * @throws ServiceException the service exception
   */
  public static <T extends IDataObject<? extends Number>> void validate(final Collection<T> values)
      throws ServiceException {
    if (values == null) {
      throw new IllegalArgumentException("[entities: can not be null");
    }
  }

  /**
   * Validate.
   * @param key the key
   * @throws ServiceException the service exception
   */
  public static void validate(final Object key) throws ServiceException {
    if (key == null) {
      throw new IllegalArgumentException('[' + IDataObject.KEY + ": can not be null");
    }
  }

  /**
   * Validate.
   * @param key the key
   * @throws ServiceException the service exception
   */
  public static void validate(final Number key) throws ServiceException {
    if (key == null) {
      throw new IllegalArgumentException('[' + IDataObject.KEY + ": can not be null");
    }

    if (key.longValue() <= 0) {
      throw new IllegalArgumentException('[' + IDataObject.KEY + "]:" + key
          + " must be greater than 0");
    }
  }

  /**
   * Validate.
   * @param <T> the generic type
   * @param value the value
   * @throws ServiceException the service exception
   */
  public static <T extends IDataObject<? extends Serializable>> void validate(final T value)
      throws ServiceException {
    if (value == null) {
      throw new IllegalArgumentException("[entity]: can not be null");
    }

    if (value.getKey() instanceof Number) {
      validate((Number)value.getKey());
    }
  }

  /**
   * Validate.
   * @param <T> the generic type
   * @param context the context
   * @param value the value
   * @throws ServiceException the service exception
   */
  public static <T extends IDataObject<? extends Number>> void validate(
      final IApplicationContext context, final T value) throws ServiceException {
    validate(context);
    validate(value);
  }
}
